﻿using System;
using System.Linq;
using System.Linq.Expressions;
using Infrastructure.CrossCutting.Checks;

namespace Data.Core.Specifications
{
    /// <summary>
    /// Provides a default implementation of the <see cref="IOrderBySpecification{TEntity}"/> interface.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <remarks>
    /// The <see cref="OrderBySpecification{TEntity}"/> implements Composite Specification pattern by overloading
    /// the &amp; and | (And, Or in VB.Net) operators to allow composing multiple specifications together.
    /// </remarks>
    public class OrderBySpecification<T> : IOrderBySpecification<T>
    {
        #region .ctor

        public OrderBySpecification(Expression<Func<T, object>> orderBy, OrderDirection sortDirection = OrderDirection.Ascending, Expression<Func<T, object>> thenBy = null, OrderDirection thenBySortDirection = OrderDirection.Ascending)
        {
            Check.Against<ArgumentNullException>(orderBy == null, "Expected a non null expression as a predicate for the specification.");
            Direction = sortDirection;
            ThenByDirection = thenBySortDirection;

            OrderByPredicate = orderBy;
            OrderThanByPredicate = thenBy;
        }

        public OrderBySpecification(Expression<Func<T, object>> orderBy, Expression<Func<T, object>> thenBy = null, OrderDirection thenBySortDirection = OrderDirection.Ascending) : 
            this(orderBy, OrderDirection.Ascending, thenBy, thenBySortDirection)
        {
            
        }

        #endregion

        #region Implementation of IOrderBySpecification<T>

        public OrderDirection Direction { get; private set; }
        public OrderDirection ThenByDirection { get; private set; }

        /// <summary>
        /// Gets the expression compiled that encapsulates the criteria of the specification.
        /// </summary>
        public Expression<Func<T, object>> OrderByPredicate { get; private set; }

        /// <summary>
        /// Gets the expression compiled that encapsulates the criteria of the specification.
        /// </summary>
        public Expression<Func<T, object>> OrderThanByPredicate { get; private set; }

        public IOrderedQueryable<T> ApplyOrderBy(IQueryable<T> query)
        {
            var result = Direction == OrderDirection.Ascending
                             ? query.OrderBy(OrderByPredicate)
                             : query.OrderByDescending(OrderByPredicate);

            if(OrderThanByPredicate != null)
                result = ThenByDirection == OrderDirection.Ascending
                             ? result.ThenBy(OrderThanByPredicate)
                             : result.ThenByDescending(OrderThanByPredicate);

            return result;
        }

        #endregion
    }

    public enum OrderDirection
    {
        Ascending,
        Descending
    }
}
